using System;
using System.Diagnostics.CodeAnalysis;
using Godot;

namespace Arcane.Source.TileMatch;

public struct TilePos
{
    public int col, row;

    public TilePos(int col, int row)
    {
        this.col = col;
        this.row = row;
    }

    public readonly void Deconstruct(out int col, out int row)
    {
        col = this.col;
        row = this.row;
    }

    public int AsIndex(int boardWidth) => row * boardWidth + col;

    public readonly TilePos Offset(TilePos delta) => new(delta.col + col, delta.row + row);

    public readonly TilePos Left() => new(col - 1, row);
    public readonly TilePos Right() => new(col + 1, row);
    public readonly TilePos Top() => new(col, row - 1);
    public readonly TilePos Bottom() => new(col, row + 1);

    public readonly TilePos Side(TileSide side)
    {
        return side switch
        {
            TileSide.Right => Right(),
            TileSide.Top => Top(),
            TileSide.Left => Left(),
            TileSide.Bottom => Bottom(),
            _ => new(-1, -1)
        };
    }

    public readonly bool IsLeft(TilePos other) => other.col - col == -1 && other.row == row;
    public readonly bool IsRight(TilePos other) => other.col - col == 1 && other.row == row;
    public readonly bool IsTop(TilePos other) => other.col == col && other.row - row == -1;
    public readonly bool IsBottom(TilePos other) => other.col == col && other.row - row == 1;

    public readonly bool IsNeighbor(TilePos other) => IsLeft(other) || IsTop(other) || IsRight(other) || IsBottom(other);
    

    public readonly int SqDistance(TilePos other)
    {
        var (dc, dr) = (col - other.col, row - other.row);
        return dc * dc + dr * dr;
    }
    public readonly float Distance(TilePos other) => MathF.Sqrt(SqDistance(other));
    public readonly int MDistance(TilePos other) => Math.Abs(other.col - col) + Math.Abs(other.row - row);

    public static bool operator == (TilePos lhs, TilePos rhs)
    {
        return lhs.col == rhs.col && lhs.row == rhs.row;
    }

    public static bool operator != (TilePos lhs, TilePos rhs)
    {
        return lhs.col != rhs.col || lhs.row != rhs.row;
    }

    public override readonly bool Equals([NotNullWhen(true)] object obj) => obj is TilePos pos && pos == this;
    public override readonly int GetHashCode() => (col + row) * (col + row + 1) / 2 + row;


    public static implicit operator (int, int) (TilePos pos) => (pos.col, pos.row);
    public static implicit operator TilePos ((int c, int r) tuple) => new(tuple.c, tuple.r);

    public override readonly string ToString() => $"({col}, {row})";

    public readonly Vector2 ToVector2() => new(col, row);
    public readonly Vector2 ToCellCenter() => new(col + 0.5f, row + 0.5f);
}